""" Functions related to drawing things to surfaces.

    This module defines functions related to drawing basic shapes,
    text, and other things to the screen or to other surfaces.
    
    You must call sbak.init() or sbak.video.init() before you can use
    this module. Text drawing will only work if the pygame.font module
    is available and only after the init() function of this module has
    been called.
    
    Most of the functions defined here are wrappers around functions
    defined in pygame.draw which will accept None as the target surface
    to mean the screen (display) surface.
    
    For convenience, the original Pygame versions of functions may be
    accessed under the same names as the wrappers versions but with
    a "_" (underscore) appended to the end. For example,
    
    >>> import pygame, sbak
    >>> # Sbak's `draw.rect_` is really Pygame's `draw.rect`
    >>> sbak.draw.rect_ is pygame.draw.rect
    True
    
    This module makes use of and aliases pygame.Color. That name is
    defined differently depending in the version of Pygame installed
    on your system, but this module and other modules throughout
    ScrollBack are designed in a way that these differences should
    have little effect (if any) on the way ScrollBack behaves. However,
    you are advised to check the documentation for your version of
    Pygame to see how Color will behave.
    
    ~~~ Module Attributes ~~~
    
    -- Constants --
    
    DEFAULT_FONT_SIZE:      Size of default font (in pixels)
    
    DEFAULT_FONT_NAMES:     Sequence of font names used to get the
                            default font.
    
    -- Variables --
    
    default_font:           Font used by the text-drawing functions,
                            and the font used as the initial value for
                            `sbak.widget.Style.base`.
                            
                            This variable only exists if `pygame.font`
                            is available.
                            
                            This is NOT included when "import *" syntax
                            is used.
"""

import pygame

from pygame import Color
from pygame.draw import (
    rect as rect_,
    line as line_,
    circle as circle_
)
import video

# FIXME: These are probably not good default fonts as they most likely won't
# appear on many systems. They should be updated to values that are more
# common.
DEFAULT_FONT_SIZE = 16
DEFAULT_FONT_NAMES = '"DejaVu Sans", "FreeSans", "Sans Serif", "MS Sans", "Sans"'

__all__ = (
    'DEFAULT_FONT_SIZE',
    'DEFAULT_FONT_NAMES',
    'init',
    'text',
    'text_nl',
    'rect',
    'rect_',
    'line',
    'line_',
    'cirlce',
    'circle_',
    'fix_color',
    'Color'
)

def init():
    """ Initializes the drawing system.
    
        Most of the drawing functions do not need this to be called.
        However, text-related functions do.
        
        This will initialize pygame.font if it is available.
        If pygame.font is not available, a warning message will be
        printed.
    """
    global _init, default_font
    if not _init:
        try:
            pygame.font
        except:
            debug.warn("pygame.font unavailable; text drawing will not be possible.")
        else:
            pygame.font.init()
            default_font = pygame.font.SysFont(DEFAULT_FONT_NAMES,
                                               DEFAULT_FONT_SIZE)
            
            
        _init = True

def fix_color(*vals):
    """ Generates a color from a given value.
    
        This function will accept any of the following:
        
        * A string of any of the following forms:
          * Web hex style, no alpha - "#RRBBGG"
          * Web hex style, with alpha - "#RRBBGGAA"
          * 0x hex style, no alpha - "0xRRBBGG"
          * 0x hex style, with alpha - "0xRRBBGGAA"
        * The name of a color as a string, such as "red", "white", etc.
        * 3-4 integers in the order R, B, G or R, B, G, A.
        * A sequence of 3-4 integers of the forms:
          * (R,G,B), or
          * (R,G,B,A)
        
        The return value is a value returned by pygame.Color after
        the value passed is converted to a format that pygame.Color
        can understand.
        
        This function should only pass values to pygame.Color that
        should work for the version of Pygame that is installed on the
        host system and will return the corresponding value. This can
        be used to take advantage of the Color class implemented in
        Pygame 1.8 while still maintaining backwards compatibility with
        Pygame 1.7.
        
        If this function causes TypeError or ValueError to
        be raised, make sure that the input value fits the description
        of one of the possible value types listed above. If the input
        value is correct and pygame.Color still complains, please file
        a bug report.
        
        FIXME: This func could be simplified a lot probably
    """
    # 1. Try string.
    if isinstance(vals[0], basestring):
        col = vals[0]
    
    # 2. Try integers 
    elif len(vals) in (3,4):
        if len(vals) == 3:
            vals = vals + (255,)
        col = ('0x'+('%02X'*4)) % tuple(vals)
    
    # 3. Try a tuple
    elif len(vals)==1 and len(vals[0]) in (3,4):
        vals = vals[0]
        if len(vals) == 3:
            vals = vals + (255,)
        col = ('0x'+('%02X'*4)) % tuple(vals)
    
    # 4. Unsupported formats
    else:
        raise TypeError("Unsupported input format. See documentation for "
                        "details.")
    
    # Convert to whatever Pygame uses
    col = Color(col)
    
    return col

def rect(dest, color,rect,width=0):
    """ Draw a rectangle to the given surface.
    
        dest:   Surface onto which the rect will be drawn. Pass None to
                draw to the screen.
                
        color:  Color of the rectangle.
                
        rect:   Rect to draw. Must be a Rect or Rect-compatible
                sequence. Pass None to indicate the whole region of the
                screen.
                
        width:  Width of the outline. If 0, the rectangle will be
                filled. If 1 or greater, the rectangle will be an
                outline with the given thickness in pixels.
                
        Returns the region of the destination affected as a Rect.
    """
    dest = dest or video.get_screen()
    color = fix_color(color)
    if rect is None:
        if width == 0:
            return dest.fill(color)
        rect = dest.get_rect()
    if width == 0:
        return dest.fill(color, rect)
    return rect_(dest,color,rect,width)

def circle(dest, color, pos, radius, width=0):
    """ Draw a circle centered around a point.
        
        dest:   Surface onto which the circle will be drawn. Pass None
                to draw to the screen.
        pos:    Position on destination at which the circle will be
                drawn.
        radius: Radius of the circle. Must be integer.
        width:  Width of the outline. If 0, the circle will be
                filled. If 1 or greater, the rectangle will be an
                outline with the given thickness in pixels.
                
        Returns the region of the destination effected as Rect.
    """
    dest = dest or video.get_screen()
    return circle_(dest,color,pos,radius,width)

def line(dest,color,start,end,width=1):
    """ Draw a line from one point to another.
    
        dest:   Surface onto which the rect will be drawn. Pass None to
                draw to the screen.
        start:  Starting point of the line on the destination.
        end:    Ending point of the line on the destination.
        width:  Width of the line in pixels. Default is 1.
        
        Returns the region of the destination effected as Rect.
    """
    dest = dest or video.get_screen()
    return line_(dest,color,start,end,width)

def image(source, dest=None, offset=None, region=None):
    """ Draws a surface, Image, or image-compatible object to a surface.
        
        source: A drawable object. This includes Surfaces, Images and
                Image-compatible objects.
        dest:   A surface onto which the drawable object will be drawn.
                If None is given, the object will be drawn to the
                screen.
        offset: Position on the destination surface at which the
                drawable will be drawn. Should be an integer pair or
                None. The meaning of None depends on the type of
                drawable. If it is a Surface, the offset becomes 0,0,
                but if it is an Image or compatible object, None is
                passed on to the draw function for the offset.
        region: The reagion of the drawable to draw. If None the whole
                thing will be drawn.
        
        This acts as a kind of "wrapper" to sort out the differences between
        Surface.blit and Image.draw. The correct method for getting `source`
        onto `dest` will be determined automatically.
    """
    # Don't draw None
    if source is None:
        return
    # The default destination surface is the screen
    dest = dest or video.get_screen()
    # For drawing image-like objects
    if hasattr(source,'draw'):
        source.draw(dest, offset, region)
    # For drawing surfaces
    elif region is not None:
        if offset is None:
            offset = 0,0
        dest.blit(source, offset, region)
    else:
        if offset is None:
            offset = 0,0
        dest.blit(source, offset)

def text(dest, pos, text, font=None, fgcolor=fix_color(255,255,255),
         bgcolor=None, aa=False, align='left', valign='bottom'):
    """ Draw text to a surface.
        
        dest:       Surface onto which the text will be drawn. Pass
                    None to draw to the screen.
                
        pos:        Position on destination at which the text will be
                    drawn. If a sequence of more than two values is
                    passed, only the first two values will be used.
                
        text:       Text to draw.
        
        font:       The font to use to draw the text. If None
                    (default) the default font will be used.
                
        fgcolor:    Color of text. Default is white. Must be a
                    value that can be processed by fix_color.
                
        bgcolor:    Color of background (the rectangular area
                    occupied by the text). If None (default), no
                    background color will be drawn.
                
        aa:         Whether or not to use anti-aliasing to draw the
                    text. Default is False (don't use
                    anti-aliasing.)
                    
        align:      Text horizontal alignment. Allowed values are:
                    - "left" (default)
                    - "right"
                    - "center"
                    
        valign:     Text vertical alignment. Allowed values are:
                    - "top"
                    - "middle"
                    - "bottom" (default)
        
        Be aware that text drawing may be very slow as the text
        must be rendered every time this function is called. If you
        need to draw the exact same text many times in exactly the
        same way, it may make more sense to use `pygame.font.Font`.
        
        This function will fail if `pygame.font` is unavailable.
        
        Returns the affected region.
    """
    dest = dest or video.get_screen()
    if font is None:
        font = default_font
    
    fgcolor = fix_color(fgcolor)
    
    if bgcolor is not None:
        bgcolor = fix_color(bgcolor)
        rendered = font.render(text,aa,fgcolor,bgcolor)
    else:
        rendered = font.render(text,aa,fgcolor)
    
    pos = map(int, pos[:2])
    w,h = rendered.get_size()
    
    try:
        align.strip().lower()
    except AttributeError:
        raise TypeError("align must be string.")
    if align == 'left':
        pass
    elif align == 'center':
        pos[0] -= w/2
    elif align == 'right':
        pos[0] -= w
    else:
        raise ValueError("invalid value for align: %r" % align)
        
    try:
        valign.strip().lower()
    except AttributeError:
        raise TypeError("valign must be string.")
    if valign == "bottom":
        pass
    elif valign == "middle":
        pos[1] -= h/2
    elif valign == "top":
        pos[1] -= h
    else:
        raise ValueError("invalid value for valign: %r" % valign)
    
    return dest.blit(rendered, pos)

def text_nl(dest, pos, text, font=None, fgcolor=Color("white"),
            bgcolor=None, aa=False):
    """ Draw multiline text to a surface.
    
        The "nl" stands for "NewLines" (the \\n character).
    
        dest:   Surface onto which the text will be drawn. Pass
                None to draw to the screen.
        pos:    Position on destination at which the text will be
                drawn.
        text:   Text to draw. The text will be split into multiple
                lines wherever a newline (\\n) character is
                encountered. Also, 
        font:   The font to use to draw the text. If None (default)
                the default font will be used.
        fgcolor:
                Color of text. Default is white.
        bgcolor:
                Color of background (the rectangular area occupied
                by the text). If None (Default) no background color
                will be drawn.
        aa:     Whether or not to use anti-aliasing to draw the
                text. Default is False (don't use anti-aliasing.)
        
        This function will fail if `pygame.font` is unavailable.
        
        TODO: Return affected region.
        TODO: Allow for text alignment.
    """
    dest = dest or video.get_screen()
    if font is None:
        font = default_font
    
    text = text.replace('\r\n','\n').replace('\r','\n')
    lines = text.split('\n')
    h = font.get_height()
    x,y = pos
    
    render = font.render
    blit = dest.blit
    
    if bgcolor is not None:
        for n,L in enumerate(lines):
            blit(render(L, aa, fgcolor, bgcolor), (x,y+h*n))
    else:
        for n,L in enumerate(lines):
            blit(render(L, aa, fgcolor), (x,y+h*n))

# Used to determine if the drawing module is initialized.
_init = False